/*
* Copyright (c) 2011 adVolition
*
* Permission is hereby granted, free of charge, to any person
* obtaining a clone of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* clone, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* Except as contained in this notice, the name(s) of the above
* copyright holders shall not be used in advertising or otherwise to
* promote the sale, use or other dealings in this Software without prior
* written authorization.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
 */

package NeuralNetwork;
import com.thoughtworks.xstream.annotations.*;
import java.util.ArrayList;


@XStreamAlias("bundle") public class Bundle {

    protected transient Bundle _parentBundle;
    protected transient Mutability _parentFidelity;

    @XStreamImplicit protected ArrayList<Node> _nodes;
    @XStreamImplicit protected ArrayList<Edge> _edges;
    @XStreamImplicit protected ArrayList<Bundle> _bundles;

    public Bundle() {
         configureObject();
    }

    public Bundle(Bundle parentBundle) {
        _parentBundle = parentBundle;
        configureObject();
    }

    public Object readResolve() {
        configureObject();
        return this;
    }
    
    /** Used by class constructor and readResolve routines to ensure that newly created
     * objects are configured properly.
     * @return 
     **/
    private void configureObject() {
        
        if (_parentBundle == null) _parentBundle = this;
        if (_parentFidelity == null) _parentFidelity = new Mutability();
        
        if (_nodes == null) {
            _nodes = new ArrayList<Node>();
            //addNode(new Node(this));
        }
        
        if (_edges == null) {
            _edges = new ArrayList<Edge>();
            //addEdge(new Edge(this));
        }

        if (_bundles == null) {
            _bundles = new ArrayList<Bundle>();
        }
        
    }

    /**
     * Assigns a parent to each of the child and its children.
     * @param parent
     */
    public void assignParent(Bundle parent) {
        _parentBundle = (Bundle)parent;
        _parentFidelity = parent.getFidelity();

        for (int i = 0; i < _nodes.size(); i++) {
            _nodes.get(i).assignParent(this);
        }
        
        for (int i = 0; i < _edges.size(); i++) {
            _edges.get(i).assignParent(this);
        }
        for (int i = 0; i < _bundles.size(); i++) {
            _bundles.get(i).assignParent(this);
        }
    }

    public int getRandomNodeID() {
        return (int)(Math.random() * _nodes.size());
    }

    public Bundle getParent() {
        return _parentBundle;
    }

    public Mutability getFidelity() {
        return _parentFidelity;
    }

    public void computeFutureState(Double timeIncrement) {
        for (int i = 0; i < _nodes.size(); i ++) _nodes.get(i).computeFutureState(timeIncrement);
        for (int i = 0; i < _edges.size(); i ++) _edges.get(i).computeFutureState(timeIncrement);
        for (int i = 0; i < _bundles.size(); i ++) _bundles.get(i).computeFutureState(timeIncrement);
    }

    public void implementFutureState() {
        for (int i = 0; i < _nodes.size(); i ++) _nodes.get(i).implementFutureState();
        for (int i = 0; i < _edges.size(); i ++) _edges.get(i).implementFutureState();
        for (int i = 0; i < _bundles.size(); i ++) _bundles.get(i).implementFutureState();

    }

    /**
     * This is an error-prone method for cloning the object.  It is used to generate mutant robot offspring.
     * @return
     */
    public Bundle getClone(Bundle parentBundle) {
        Bundle newBundle = new Bundle(parentBundle);

        // Clone nodes.
        /*int nodeCount = _nodes.size();
        if (Math.random() < _parentFidelity.getDeleteNode()) nodeCount --;

        for (int i = 0; i < nodeCount; i ++) {
            newBundle.addNode(_nodes.get(i).getClone(newBundle));
        }

        if (Math.random() < _parentFidelity.getAddNode()) newBundle.addNode(new Node(newBundle));
   
         */
        int count = 0;
        while (count < _nodes.size()) {
            newBundle.addNode(_nodes.get(count).getClone(newBundle));
            count = nodeSlipCount(count);
        }
        if (Math.random() < 0.8) newBundle.addNode(new Node(newBundle));
    
        
        // Clone edges.
        count = 0;
        while (count < _edges.size()) {
            newBundle.addEdge(_edges.get(count).getClone(newBundle));
            count = edgeSlipCount(count);
        }

        // Clone bundles.
        count = 0;
        while (count < _bundles.size()) {
            newBundle.addBundle(_bundles.get(count).getClone(newBundle));
            count = bundleSlipCount(count);
        }

        if (Math.random() < _parentFidelity.getAddBundle()) newBundle.addBundle(new Bundle(newBundle));
        
        return newBundle;
    }

    
    /**
     * Occasionally skips or double increments a count.  Used to copy or delete
     * edges when cloning a bundle.
     */
    protected int nodeSlipCount(int count) {
        if (Math.random() < _parentFidelity.getDeleteNode()) {
            return count + 2;
        } else if (Math.random() < _parentFidelity.getCloneEdge()) {
            return count;
        }
        return count + 1;
    }
    
    /**
     * Occasionally skips or double increments a count.  Used to copy or delete
     * edges when cloning a bundle.
     */
    protected int edgeSlipCount(int count) {
        if (Math.random() < _parentFidelity.getDeleteEdge()) {
            return count + 2;
        } else if (Math.random() < _parentFidelity.getCloneEdge()) {
            return count;
        }
        return count + 1;
    }

    /**
     * Occasionally skips or double increments a count.  Used to copy or delete
     * bundles when cloning a bundle.
     */
    protected int bundleSlipCount(int count) {
        if (Math.random() < _parentFidelity.getDeleteBundle()) {
            return count + 2;
        } else if (Math.random() < _parentFidelity.getCloneBundle()) {
            return count;
        }
        return count + 1;
    }
    
    public Node getNode(int nodeID) {
        if (nodeID < 0) {
            return _parentBundle.getNode(Math.abs(nodeID));
        } else {
            return _nodes.get(nodeID);
        }
    }

    public int getNodeCount() {
        return _nodes.size();
    }

    public void addEdge(Edge edge) {
        _edges.add(edge);
    }

    public Edge getLastEdge() {
        return _edges.get(_edges.size() - 1);
    }
    
    /**
     * Adds a number of nodes to the bundle and sets their values to 0.0.
     * @param number
     */
    public void addNode(Node node) {
         _nodes.add(node);
    }

    public void addBundle(Bundle bundle) {
        _bundles.add(bundle);
    }

    public Double readNode(int index) {
       if (index >= 0 && index < _nodes.size()) {
           return _nodes.get(index).getValue();
       } else {
           return 0.0;
       }
    }
    
    public void reset() {
        for (int i = 0; i < _nodes.size(); i ++) {
            _nodes.get(i).reset();
        }
        
        for (int i = 0; i < _edges.size(); i ++) {
            _edges.get(i).reset();
        }
        
        for (int i = 0; i < _bundles.size(); i ++) {
            _bundles.get(i).reset();
        }
    }

    /**
     * Returns the total number of nodes and edges in this bundle and all nested bundles.
     * @return 
     */
    public int size() {
        int size = 0;
        size += _nodes.size();
        size += _edges.size();
        //size += _bundles.size();
        for (int i = 0; i < _bundles.size(); i ++) {
            size += _bundles.get(i).size();
        }
        return size;
    }

}
